﻿using JQuery.Net;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition.Hosting;
using System.IO;
using System.Reflection;
using System.Text;
using System.Web;
using System.Web.Caching;
using System.Web.Configuration;
using System.Web.Mvc;
using System.Web.UI;
using Base = System.Web.UI.Page;

namespace JQuery
{
    public class Module 
        : IHttpModule
    {
        #region "module constants"
        
        #endregion

        #region "static properties"
        public static ExtensionContainer Composition = null;
        public static Resource Manager = null;
        #endregion

        #region "properties"
        
        /// <summary>
        /// 
        /// </summary>
        //internal ResourceManager Manager { get { return manager; } }
        #endregion

        #region "field variables"
        //private ResourceManager manager = null;
        #endregion

        /// <summary>
        /// You will need to configure this module in the web.config file of your
        /// web and register it with IIS before being able to use it. For more information
        /// see the following link: http://go.microsoft.com/?linkid=8101007
        /// </summary>
        #region IHttpModule Members

        public void Dispose()
        {
            //clean-up code here.
        }

        public void Init(HttpApplication context)
        {
            /// this area is where we would initialize and data
            /// access the web config and locate the configuration section defined to register plugins for JQuery.Net
            JQNConfigSection Section = (JQNConfigSection)WebConfigurationManager.GetSection(typeof(JQNConfigSection).Name);

            if (Section == null)
            {
                Section = new JQNConfigSection();
            }

            if (Composition == null)
            {   /// the Composition Container has not been instanciated. we do this once for the entire life time of the web application.
                var catalog = new AggregateCatalog();
                /// register the configured plugins
                if (Section.EnablePlugins)
                {
                    foreach (PluginElement Plugin in Section.Plugins)
                    {
                        try
                        {
                            var asm = new AssemblyCatalog(Assembly.Load(Plugin.AssemblyFullName));

                            catalog.Catalogs.Add(asm);
                        }
                        catch (Exception ex)
                        {   /// trace an error for every assembly that fails to load.
                            Utilities.Utility.TraceEvent("JQuery.Net.Module", ex, Utilities.TraceSeverity.Error);
                        }
                    }

                }
                /// last but not least process the JQuery.Net Assembly
                catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));

                Composition = Composition.IsNotNull(new ExtensionContainer(catalog), (X) => X);

                if (Config.MVCEnabled)
                {   // clean it out or at a later date make this configurable
                    ViewEngines.Engines.Clear();
                    // we need to register the jquery view engine
                    ViewEngines.Engines.Add(new MVC.QueryEngine());
                    // the following engine will be used in context with a database driven view engine
                    if (Config.EnableMVCSchemaEngine)
                    {
                        ViewEngines.Engines.Add(new MVC.QuerySchemaEngine());
                    }
                }

                Utilities.Utility.TraceEvent("JQuery.Net.Module", "Finished Initializing the Composition Container!");
            }

            if (Manager == null)
            {
                Manager = Resource.Initialize(context);
            }
            else
            {   // refresh the application context on the resource manager.
                Manager.TheApp = context;
            }

            context.PreRequestHandlerExecute += new EventHandler(context_PreRequestHandlerExecute);
            context.BeginRequest += new EventHandler(context_BeginRequest);
        }

        void context_BeginRequest(object sender, EventArgs e)
        {
            HttpApplication WebApp = sender as HttpApplication;

            //HttpContext.SetServerInstance(WebApp, true);

            String 
                path = WebApp.Request.Url.AbsolutePath,
                id = String.Empty,
                type = String.Empty;
#if DEBUG
            WebApp.Response.ExpiresAbsolute = DateTime.Now.AddMinutes(5);
#else
            WebApp.Response.ExpiresAbsolute = DateTime.Now.AddYears(1);
#endif
            WebApp.Response.ClearContent();
            if (path.StartsWith("/JQuery.Net", StringComparison.CurrentCultureIgnoreCase))
            {   /// request was prompted from JQuery.Net reference
                if (path.StartsWith("/JQuery.Net/Script", StringComparison.CurrentCultureIgnoreCase))
                {   /// request is for a JQuery.Net Cached Resource
                    id = WebApp.Server.UrlDecode(path.Substring(path.LastIndexOf('/') + 1));
                    type = Resource.ContentTypes.javascript;
                }
            }
            else if (path.EndsWith(".css", StringComparison.CurrentCultureIgnoreCase))
            {   /// special treatment for css files
                id = path.Substring(path.LastIndexOf('/') + 1, (path.LastIndexOf('.') - path.LastIndexOf('/')) - 1);
                type = Resource.ContentTypes.css;
            }

            if (Manager.Fetch(id, type, WebApp.Response))
            {   // if we can successfully fetch the resource then we can end the response, if not pass on the request to the other modules/handlers
                WebApp.Response.End();
            }
        }

        void context_PreRequestHandlerExecute(object sender, EventArgs e)
        {
            if (!Config.MVCEnabled && HttpContext.Current.CurrentHandler != null && (HttpContext.Current.CurrentHandler is Base || HttpContext.Current.CurrentHandler.GetType().IsSubclassOf(typeof(Base))))
            {   // these page events will not work in a mvc application, we will need some type of helper method for a view to make this work.
                var handler = HttpContext.Current.CurrentHandler as Base;

                handler.InitComplete += new EventHandler(handler_InitComplete);
                handler.PreRenderComplete += new EventHandler(handler_PreRenderComplete);
            }
        }
        
        #endregion

        #region "page handler delegates"
        /// <summary>
        /// this event delegate will be used to register all the startup scripts after all pre-rendering is complete.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void handler_PreRenderComplete(object sender, EventArgs e)
        {
            var Script = ((Base)sender).ClientScript;

            using (StringWriter oWriter = new StringWriter())
            {
                foreach (IPlugin oPlugin in Composition.JQPlugins)
                {
                    oPlugin.RegisterLibraryInitializer(oWriter);
                }

                foreach (IjQNetAPI ctrl in ((Base)sender).Controls.ByInterface<IjQNetAPI>())
                {
                    ctrl.RegisterStartupScript(oWriter);
                }
                /// wrap the startup script in a document ready event function
                String startup = String.Format("$(function() {{\r\n{0}\r\n}});", oWriter.ToString());

                if (Config.IsMinimized)
                {
                    UTF8Encoding oUTF = new UTF8Encoding(true);

                    using (JSMinimizer oMinimizer = new JSMinimizer(oUTF.GetBytes(startup), Resource.ContentTypes.javascript))
                    {
                        startup = oUTF.GetString(oMinimizer.ToArray());
                    }
                }

                Script.RegisterStartupScript(GetType(), "jquery_start", startup, true);
            }
        }
        /// <summary>
        /// this event delegate will be used to register all the script includes after initialization is complete.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void handler_InitComplete(object sender, EventArgs e)
        {
            Base Page = ((Base)sender);
            /// first things first register the jquery library
            if (Page == null || Page.IsCallback)
                return; /// if we are processing a callback abort this handler.

            var Script = Page.ClientScript;

            // first thing first register the jquery api
            Page.RegisterResourceInclude("api");

            foreach (IPlugin oPlugin in Composition.JQPlugins)
            {
                oPlugin.RegisterLibrary(Manager, Page);
            }

            foreach (IjQNetAPI ctrl in Page.Controls.ByInterface<IjQNetAPI>())
            {
                JQNRegistration oRegister = new JQNRegistration();

                ctrl.RegisterScriptIncludes(oRegister);

                string key = Manager.GetResourceKey("ui.core");

                if (oRegister.Core && Manager[key] != null && Page.ResourceIncludes()[key] == null)
                {
                    Page.RegisterResourceInclude("ui.core");
                }

                key = Manager.GetResourceKey("ui.mouse");

                if (oRegister.Mouse && Manager[key] != null && Page.ResourceIncludes()[key] == null)
                {
                    Page.RegisterResourceInclude("ui.mouse");
                }

                key = Manager.GetResourceKey("ui.position");

                if (oRegister.Position && Manager[key] != null && Page.ResourceIncludes()[key] == null)
                {
                    Page.RegisterResourceInclude("ui.position");
                }

                key = Manager.GetResourceKey("ui.widget");

                if (oRegister.Widget && Manager[key] != null && Page.ResourceIncludes()[key] == null)
                {
                    Page.RegisterResourceInclude("ui.widget");
                }
            }

            if (Config.IsOptimized)
            {   /// this is were we register a actual script include for a http handler that will gather all the includes together and pack them into one script file to download
                string
                    the_script_path = Page.Server.MapPath(Page.ResolveUrl(Manager.GeneratedScriptPath)),
                    file_path = Page.Request.FilePath,
                    file_name = file_path.Substring(file_path.LastIndexOf("/") + 1).Replace(".aspx", "").ToLower(),
                    cache_key = Manager.GetResourceKey(file_name, Resource.ContentTypes.javascript);

                List<Resource.Content> Resources = Page.ResourceIncludes().ResourceList(Resource.ContentTypes.javascript);

                if (!Convert.ToBoolean(Manager.Cache[cache_key]) && Resources.Count > 0)
                {
                    if (!Manager.GeneratedScriptPathExists)
                    {
                        Directory.CreateDirectory(the_script_path);
                    }

                    using (StreamWriter oWriter = new StreamWriter(File.Open(String.Format("{0}\\{1}.js", the_script_path, file_name), FileMode.Create)))
                    {
                        foreach (var Res in Resources)
                        {
                            Res.Body(oWriter);
                        }

                        oWriter.Flush();
                    }
                    // register the optimized file with the resource manager
                    Manager.Add(file_name, String.Format("{0}/{1}.js", Manager.GeneratedScriptPath, file_name), Resource.ContentTypes.javascript);
                    // we have generated a file and to prevent this file from being regenerated for every request we are going to set a flag in cache
                    Manager.Cache.Add(cache_key, true, new CacheDependency(Page.Server.MapPath(Page.Request.Url.AbsolutePath)),
                        Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration, CacheItemPriority.AboveNormal, null);
                }
                // remove all include files for javascript and replace them with the merged file
                Page.ResourceIncludes().Clear(Resource.ContentTypes.javascript);
                // tag this resource to the page
                Page.RegisterResourceInclude(file_name);
            }
        }
        #endregion
    }
}
